/* Copyright (c) 2008 Google Inc.
*  Copyright (c) 2009 Pierre Henri Kuate.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System.Collections.Generic;
using System;
using System.Collections;
using System.IO;
using org.opensocial.data;

namespace org.opensocial.client
{
    /// <summary> An object which provides methods for indirectly interacting with the
    /// OpenSocial RESTful and/or JSON-RPC APIs exposed by a container. Clients
    /// can create a new instance of this class to fetch profile information for a
    /// particular user, the profile information for friends of a particular user,
    /// and App Data from an application's persistent data store. This class also
    /// exposes static methods for creating OpenSocialRequest instances, which
    /// can be added to OpenSocialBatchRequest instances, which is ideal for
    /// requesting bulk data from containers exposing an RPC endpoint.
    /// </summary>
    /// <author>  Jason Cooper
    /// </author>
    [Serializable] // Required to keep the client in the ASP.NET Session State when its mode != InProc
    public class OpenSocialClient
    {
        public static class Properties
        {
            public const string OWNER_ID = "opensocial_owner_id";
            public const string VIEWER_ID = "opensocial_viewer_id";
            public const string APP_ID = "opensocial_app_id";
            public const string APP_URL = "opensocial_app_url";
            public const string CONSUMER_KEY = "oauth_consumer_key";
            public const string CONSUMER_SECRET = "oauth_consumer_secret";

            public const string VIEW = "container_view";
            public const string REST_BASE_URI = "container_rest_base_uri";
            public const string RPC_ENDPOINT = "container_rpc_endpoint";
            public const string CONTENT_TYPE = "content_type";

            public const string DOMAIN = "domain";
            public const string TOKEN = "token";
            public const string ACCESS_TOKEN = "oauth_token";
            public const string ACCESS_TOKEN_SECRET = "oauth_token_secret";
        }

        private readonly Guid _id = Guid.NewGuid();

        private readonly IDictionary<string, string> properties = new Dictionary<string, string>();


        public Guid Id
        {
            get { return _id; }
        }

        public string OwnerId
        {
            get { return getProperty(Properties.OWNER_ID); }
            set { setProperty(Properties.OWNER_ID, value); }
        }

        public string ViewerId
        {
            get { return getProperty(Properties.VIEWER_ID); }
            set { setProperty(Properties.VIEWER_ID, value); }
        }

        public string AppId
        {
            get { return getProperty(Properties.APP_ID); }
            set { setProperty(Properties.APP_ID, value); }
        }

        public string AppUrl
        {
            get { return getProperty(Properties.APP_URL); }
            set { setProperty(Properties.APP_URL, value); }
        }

        public string ConsumerKey
        {
            get { return getProperty(Properties.CONSUMER_KEY); }
            set { setProperty(Properties.CONSUMER_KEY, value); }
        }

        public string ConsumerSecret
        {
            get { return getProperty(Properties.CONSUMER_SECRET); }
            set { setProperty(Properties.CONSUMER_SECRET, value); }
        }

        public string RestBaseUri
        {
            get { return getProperty(Properties.REST_BASE_URI); }
            set { setProperty(Properties.REST_BASE_URI, value); }
        }

        public string RpcEndpoint
        {
            get { return getProperty(Properties.RPC_ENDPOINT); }
            set { setProperty(Properties.RPC_ENDPOINT, value); }
        }

        public string ContentType
        {
            get { return getProperty(Properties.CONTENT_TYPE); }
            set { setProperty(Properties.CONTENT_TYPE, value); }
        }

        public string View
        {
            get { return getProperty(Properties.VIEW); }
            set { setProperty(Properties.VIEW, value); }
        }

        public bool UseRpc
        {
            get { return RpcEndpoint != null; }
        }



        public OpenSocialClient() : this(string.Empty)
        {
        }

        public OpenSocialClient(string domain)
        {
            ContentType = "application/json";
            setProperty(Properties.DOMAIN, domain);
        }

        public OpenSocialClient(Gadget gadget)
            : this(gadget.Container.providerName)
        {
            ConsumerKey = gadget.OutgoingConsumerKey;
            ConsumerSecret = gadget.ConsumerSecret;
            RestBaseUri = gadget.Container.restEndpoint;
            RpcEndpoint = gadget.Container.rpcEndpoint;
            ContentType = gadget.Container.contentType;
        }


        public override int GetHashCode()
        {
            return Id.GetHashCode();
        }

        public override bool Equals(object obj)
        {
            OpenSocialClient client = obj as OpenSocialClient;
            return client != null && client.Id == Id;
        }

        public override string ToString()
        {
            return base.ToString() + "#" + Id;
        }



        /// <summary> Returns the value of the property with the passed name.
        /// 
        /// </summary>
        /// <param name="name">Name of desired property
        /// </param>
        public string getProperty(string name)
        {
            string value;
            return properties.TryGetValue(name, out value) ? value : null;
        }

        /// <summary> Adds a new property with the passed name and value.
        /// 
        /// </summary>
        /// <param name="name">Property name
        /// </param>
        /// <param name="value">Property value
        /// </param>
        public void setProperty(string name, string value)
        {
            properties[name] = value;
        }


        #region Groups

        public Response<Group> fetchGroups()
        {
            return fetchGroups("@me");
        }

        public Response<Group> fetchGroups(string userId)
        {
            return fetchGroups(userId, null);
        }

        public Response<Group> fetchGroups(IDictionary<string, OpenSocialRequestParameter> parameters)
        {
            return fetchGroups("@me", parameters);
        }

        /// <summary> Requests the groups/friend categories of the specified user
        /// and returns a collection of Group objects.
        /// </summary>
        /// <throws>  OpenSocialRequestException if there are any runtime issues with </throws>
        /// <summary>         establishing a RESTful or JSON-RPC connection or parsing the
        /// response that the container returns
        /// </summary>
        /// <throws>  JSONException </throws>
        /// <throws>  OAuthException </throws>
        /// <throws>  IOException </throws>
        /// <throws>  URISyntaxException </throws>
        public Response<Group> fetchGroups(string userId, IDictionary<string, OpenSocialRequestParameter> parameters)
        {
            if (string.IsNullOrEmpty(userId))
                throw new OpenSocialRequestException("Invalid request parameters");

            OpenSocialRequest<Group> r = newFetchGroupsRequest(userId, parameters);

            OpenSocialBatch batch = new OpenSocialBatch();
            batch.addRequest(r, "groups");

            OpenSocialResponse response = batch.send(this);

            return response.getItemAsGroupsCollection("groups", UseRpc);
        }

        public static OpenSocialRequest<Group> newFetchGroupsRequest(string userId, IDictionary<string, OpenSocialRequestParameter> parameters)
        {
            OpenSocialRequest<Group> r = new OpenSocialRequest<Group>("groups", "groups.get");

            if (parameters != null)
                r.Parameters = parameters;

            r.addParameter("userId", userId);

            return r;
        }

        #endregion


        #region Person, Friends, People
        /// <summary> Requests the profile details of the current user (i.e. the viewer as
        /// identified by the VIEWER_ID property) and returns an Person
        /// instance with all of the corresponding information.
        /// 
        /// </summary>
        /// <throws>  OpenSocialRequestException if there are any runtime issues with </throws>
        /// <summary>         establishing a RESTful or JSON-RPC connection or parsing the
        /// response that the container returns
        /// </summary>
        /// <throws>  JSONException </throws>
        /// <throws>  OAuthException </throws>
        /// <throws>  IOException </throws>
        /// <throws>  URISyntaxException </throws>
        public Person fetchPerson()
        {
            return fetchPerson("@me");
        }

        public Person fetchPerson(IDictionary<string, OpenSocialRequestParameter> parameters)
        {
            return fetchPerson("@me", parameters);
        }

        /// <summary> Requests a user's profile details and returns an Person
        /// instance with all of the corresponding information.
        /// </summary>
        /// <param name="userId">OpenSocial ID of user whose profile details are to be
        /// fetched
        /// </param>
        /// <throws>  OpenSocialRequestException if there are any runtime issues with </throws>
        /// <summary>         establishing a RESTful or JSON-RPC connection or parsing the
        /// response that the container returns
        /// </summary>
        /// <throws>  JSONException </throws>
        /// <throws>  OAuthException </throws>
        /// <throws>  IOException </throws>
        /// <throws>  URISyntaxException </throws>
        public Person fetchPerson(string userId)
        {
            return fetchPerson(userId, null);
        }

        public Person fetchPerson(string userId, IDictionary<string, OpenSocialRequestParameter> parameters)
        {
            OpenSocialResponse response = fetchPeople(userId, "@self", parameters);
            return response.getItemAsPerson("people");
        }

        /// <summary> Requests profile details for the friends of the current user and returns a
        /// Java Collection of Person instances representing the friends
        /// with all of the corresponding information.
        /// </summary>
        /// <throws>  OpenSocialRequestException if there are any runtime issues with </throws>
        /// <summary>         establishing a RESTful or JSON-RPC connection or parsing the
        /// response that the container returns
        /// </summary>
        /// <throws>  JSONException </throws>
        /// <throws>  OAuthException </throws>
        /// <throws>  IllegalAccessException </throws>
        /// <throws>  InstantiationException </throws>
        /// <throws>  IOException </throws>
        /// <throws>  URISyntaxException </throws>
        public Response<Person> fetchFriends()
        {
            return fetchFriends("@me");
        }

        /// <summary> Requests profile details for the friends of the current user and returns a
        /// Java Collection of Person instances representing the friends
        /// with all of the corresponding information.
        /// </summary>
        /// <param name="parameters">Object containing any parameters to be passed along with the request
        /// </param>
        /// <throws>  OpenSocialRequestException if there are any runtime issues with </throws>
        /// <summary>         establishing a RESTful or JSON-RPC connection or parsing the
        /// response that the container returns
        /// </summary>
        /// <throws>  JSONException </throws>
        /// <throws>  OAuthException </throws>
        /// <throws>  IllegalAccessException </throws>
        /// <throws>  InstantiationException </throws>
        /// <throws>  IOException </throws>
        /// <throws>  URISyntaxException </throws>
        public Response<Person> fetchFriends(IDictionary<string, OpenSocialRequestParameter> parameters)
        {
            return fetchFriends("@me", parameters);
        }

        /// <summary> Requests profile details for the friends of a given user and returns a
        /// Java Collection of Person instances representing the friends
        /// with all of the corresponding information.
        /// </summary>
        /// <param name="userId">OpenSocial ID of user whose friend list is to be fetched
        /// </param>
        /// <throws>  OpenSocialRequestException if there are any runtime issues with </throws>
        /// <summary>         establishing a RESTful or JSON-RPC connection or parsing the
        /// response that the container returns
        /// </summary>
        /// <throws>  JSONException </throws>
        /// <throws>  OAuthException </throws>
        /// <throws>  IllegalAccessException </throws>
        /// <throws>  InstantiationException </throws>
        /// <throws>  IOException </throws>
        /// <throws>  URISyntaxException </throws>
        public Response<Person> fetchFriends(string userId)
        {
            return fetchFriends(userId, "@friends", null);
        }

        /// <summary> Requests profile details for the friends of a given user and returns a
        /// Java Collection of Person instances representing the friends
        /// with all of the corresponding information.
        /// </summary>
        /// <param name="userId">OpenSocial ID of user whose friend list is to be fetched
        /// </param>
        /// <param name="parameters">Object containing any parameters to be passed along with the request
        /// </param>
        /// <throws>  OpenSocialRequestException if there are any runtime issues with </throws>
        /// <summary>         establishing a RESTful or JSON-RPC connection or parsing the
        /// response that the container returns
        /// </summary>
        /// <throws>  JSONException </throws>
        /// <throws>  OAuthException </throws>
        /// <throws>  IllegalAccessException </throws>
        /// <throws>  InstantiationException </throws>
        /// <throws>  IOException </throws>
        /// <throws>  URISyntaxException </throws>
        public Response<Person> fetchFriends(string userId, IDictionary<string, OpenSocialRequestParameter> parameters)
        {
            return fetchFriends(userId, "@friends", parameters);
        }

        /// <summary> Requests profile details for the friends of a given user and returns a
        /// Java Collection of Person instances representing the friends
        /// with all of the corresponding information.
        /// </summary>
        /// <param name="userId">OpenSocial ID of user whose friend list is to be fetched
        /// </param>
        /// <param name="groupId">"@self" to fetch the user's profile details or "@friends"
        /// to fetch the user's friend list, or the ID as a string of one of the user's custom groups
        /// </param>
        /// <param name="parameters">Object containing any parameters to be passed along with the request
        /// </param>
        /// <throws>  OpenSocialRequestException if there are any runtime issues with </throws>
        /// <summary>         establishing a RESTful or JSON-RPC connection or parsing the
        /// response that the container returns
        /// </summary>
        /// <throws>  JSONException </throws>
        /// <throws>  OAuthException </throws>
        /// <throws>  IllegalAccessException </throws>
        /// <throws>  InstantiationException </throws>
        /// <throws>  IOException </throws>
        /// <throws>  URISyntaxException </throws>
        public Response<Person> fetchFriends(string userId, string groupId, IDictionary<string, OpenSocialRequestParameter> parameters)
        {
            OpenSocialResponse response = fetchPeople(userId, groupId, parameters);
            return response.getItemAsPersonCollection("people", UseRpc);
        }

        /// <summary> Creates and submits a new request to retrieve the person or group of
        /// people selected by the arguments and returns the response from the
        /// container as an OpenSocialResponse object.
        /// 
        /// </summary>
        /// <param name="userId">OpenSocial ID of the request's target
        /// </param>
        /// <param name="groupId">"@self" to fetch the user's profile details or "@friends"
        /// to fetch the user's friend list, or the ID as a string of one of the user's custom groups
        /// </param>
        /// <throws>  OpenSocialRequestException if there are any runtime issues with </throws>
        /// <summary>         establishing a RESTful or JSON-RPC connection or parsing the
        /// response that the container returns
        /// </summary>
        /// <throws>  JSONException </throws>
        /// <throws>  OAuthException </throws>
        /// <throws>  IOException </throws>
        /// <throws>  URISyntaxException </throws>
        private OpenSocialResponse fetchPeople(string userId, string groupId)
        {
            return fetchPeople(userId, groupId, null);
        }

        /// <summary> Creates and submits a new request to retrieve the person or group of
        /// people selected by the arguments and returns the response from the
        /// container as an OpenSocialResponse object.
        /// 
        /// </summary>
        /// <param name="userId">OpenSocial ID of the request's target
        /// </param>
        /// <param name="groupId">"@self" to fetch the user's profile details or "@friends"
        /// to fetch the user's friend list, or the ID as a string of one of the user's custom groups
        /// </param>
        /// <param name="parameters">Object containing any parameters to be passed along  with the request
        /// </param>
        /// <throws>  OpenSocialRequestException if there are any runtime issues with </throws>
        /// <summary>         establishing a RESTful or JSON-RPC connection or parsing the
        /// response that the container returns
        /// </summary>
        /// <throws>  JSONException </throws>
        /// <throws>  OAuthException </throws>
        /// <throws>  IOException </throws>
        /// <throws>  URISyntaxException </throws>
        private OpenSocialResponse fetchPeople(string userId, string groupId, IDictionary<string, OpenSocialRequestParameter> parameters)
        {
            if (string.IsNullOrEmpty(userId) || string.IsNullOrEmpty(groupId))
                throw new OpenSocialRequestException("Invalid request parameters");

            OpenSocialRequest<Person> r = newFetchPeopleRequest(userId, groupId, parameters);

            OpenSocialBatch batch = new OpenSocialBatch();
            batch.addRequest(r, "people");

            return batch.send(this);
        }
        #endregion


        #region AppData
        /// <summary> Requests the persistent key-value pairs comprising @me's "App
        /// Data for the current application and returns a specialized
        /// OpenSocialObject instance mapping each pair to a field.
        /// 
        /// </summary>
        /// <throws>  OpenSocialRequestException if there are any runtime issues with </throws>
        /// <summary> establishing a RESTful or JSON-RPC connection or parsing the
        /// response that the container returns
        /// </summary>
        /// <throws>  JSONException </throws>
        /// <throws>  OAuthException </throws>
        /// <throws>  IOException </throws>
        /// <throws>  URISyntaxException </throws>
        public AppData fetchPersonAppData()
        {
            return fetchPersonAppData("@me", "@app");
        }

        /// <summary> Requests the persistent key-value pairs comprising a given user's "App
        /// Data for the current application and returns a specialized
        /// OpenSocialObject instance mapping each pair to a field.
        /// 
        /// </summary>
        /// <param name="userId">OpenSocial ID of user whose App Data is to be fetched
        /// </param>
        /// <throws>  OpenSocialRequestException if there are any runtime issues with </throws>
        /// <summary> establishing a RESTful or JSON-RPC connection or parsing the
        /// response that the container returns
        /// </summary>
        /// <throws>  JSONException </throws>
        /// <throws>  OAuthException </throws>
        /// <throws>  IOException </throws>
        /// <throws>  URISyntaxException </throws>
        public AppData fetchPersonAppData(string userId)
        {
            return fetchPersonAppData(userId, "@app");
        }

        /// <summary> Requests the persistent key-value pairs comprising a given user's "App
        /// Data for the application with the passed ID and returns a specialized
        /// OpenSocialObject instance mapping each pair to a field.
        /// 
        /// </summary>
        /// <param name="userId">OpenSocial ID of user whose App Data is to be fetched
        /// </param>
        /// <param name="appId">The ID of the application to fetch user App Data for
        /// or "@app" for the current application
        /// </param>
        /// <throws>  OpenSocialRequestException if there are any runtime issues with </throws>
        /// <summary>         establishing a RESTful or JSON-RPC connection or parsing the
        /// response that the container returns
        /// </summary>
        /// <throws>  JSONException </throws>
        /// <throws>  OAuthException </throws>
        /// <throws>  IOException </throws>
        /// <throws>  URISyntaxException </throws>
        public AppData fetchPersonAppData(string userId, string appId)
        {
            return fetchFriendAppData(userId, appId, "@self")[0];
        }

        /// <summary> Requests the persistent key-value pairs comprising a given user's friends' App
        /// Data for the application with the passed ID and returns a specialized
        /// OpenSocialObject instance mapping each pair to a field.
        /// 
        /// </summary>
        /// <param name="userId">OpenSocial ID of user whose App Data is to be fetched
        /// </param>
        /// <param name="appId">The ID of the application to fetch user App Data for
        /// or "@app" for the current application
        /// </param>
        /// <param name="groupId">The ID of the group for which to fetch App Data, the
        /// default is "@self". Can be "@friends" or the ID of some user created group.
        /// </param>
        /// <throws>  OpenSocialRequestException if there are any runtime issues with </throws>
        /// <summary>         establishing a RESTful or JSON-RPC connection or parsing the
        /// response that the container returns
        /// </summary>
        /// <throws>  JSONException </throws>
        /// <throws>  OAuthException </throws>
        /// <throws>  IOException </throws>
        /// <throws>  URISyntaxException </throws>
        public Response<AppData> fetchFriendAppData(string userId, string appId, string groupId)
        {
            return fetchFriendAppData(userId, appId, groupId, null);
        }

        /// <summary> Requests the persistent key-value pairs comprising a given user's "App
        /// Data for the application with the passed ID and returns a specialized
        /// OpenSocialObject instance mapping each pair to a field.
        /// 
        /// </summary>
        /// <param name="userId">OpenSocial ID of user whose App Data is to be fetched
        /// </param>
        /// <param name="appId">The ID of the application to fetch user App Data for
        /// or "@app" for the current application
        /// </param>
        /// <param name="parameters">Object containing any parameters to be passed along  with the request
        /// </param>
        /// <throws>  OpenSocialRequestException if there are any runtime issues with </throws>
        /// <summary>         establishing a RESTful or JSON-RPC connection or parsing the
        /// response that the container returns
        /// </summary>
        /// <throws>  JSONException </throws>
        /// <throws>  OAuthException </throws>
        /// <throws>  IOException </throws>
        /// <throws>  URISyntaxException </throws>
        public AppData fetchPersonAppData(string userId, string appId, IDictionary<string, OpenSocialRequestParameter> parameters)
        {
            return fetchFriendAppData(userId, appId, "@self", parameters)[0];
        }

        /// <summary> Requests the persistent key-value pairs comprising a given user's "App
        /// Data for the application with the passed ID and returns a specialized
        /// OpenSocialObject instance mapping each pair to a field.
        /// 
        /// </summary>
        /// <param name="userId">OpenSocial ID of user whose App Data is to be fetched
        /// </param>
        /// <param name="appId">The ID of the application to fetch user App Data for
        /// or "@app" for the current application
        /// </param>
        /// <param name="groupId">The ID of the group for which to fetch App Data, the
        /// default is "@self". Can be "@friends" or the ID of some user created group.
        /// </param>
        /// <param name="parameters">Object containing any parameters to be passed along  with the request
        /// </param>
        /// <throws>  OpenSocialRequestException if there are any runtime issues with </throws>
        /// <summary>         establishing a RESTful or JSON-RPC connection or parsing the
        /// response that the container returns
        /// </summary>
        /// <throws>  JSONException </throws>
        /// <throws>  OAuthException </throws>
        /// <throws>  IOException </throws>
        /// <throws>  URISyntaxException </throws>
        public Response<AppData> fetchFriendAppData(string userId, string appId, string groupId, IDictionary<string, OpenSocialRequestParameter> parameters)
        {
            OpenSocialResponse response = fetchAppData(userId, groupId, appId, parameters);
            return response.getItemAsAppData("appdata");
        }

        public void updatePersonAppData(string key, string value)
        {
            Dictionary<string, string> data = new Dictionary<string, string> { { key, value } };

            updatePersonAppData("@viewer", data);
        }

        public void updatePersonAppData(IDictionary<string, string> data)
        {
            updatePersonAppData("@viewer", data);
        }

        public void updatePersonAppData(string userId, string key, string value)
        {
            Dictionary<string, string> data = new Dictionary<string, string> { { key, value } };

            updatePersonAppData(userId, data);
        }

        public void updatePersonAppData(string userId, string key, string value, bool useLegacyFormat)
        {
            Dictionary<string, string> data = new Dictionary<string, string> { { key, value } };

            updatePersonAppData(userId, data, useLegacyFormat);
        }

        public void updatePersonAppData(string userId, IDictionary<string, string> data)
        {
            updateAppData(userId, data, true);
        }

        public void updatePersonAppData(string userId, IDictionary<string, string> data, bool useLegacyFormat)
        {
            updateAppData(userId, data, useLegacyFormat);
        }

        public void deletePersonAppData(IDictionary<string, OpenSocialRequestParameter> parameters)
        {
            deleteAppData("@me", parameters);
        }

        public void deletePersonAppData(string userId, IDictionary<string, OpenSocialRequestParameter> parameters)
        {
            deleteAppData(userId, parameters);
        }

        /// <summary> Creates and submits a new request to retrieve the persistent App Data for
        /// the person or group of people selected by the arguments for the specified
        /// application and returns the response from the container as an
        /// OpenSocialResponse object.
        /// 
        /// </summary>
        /// <param name="userId">OpenSocial ID of the request's target
        /// </param>
        /// <param name="groupId">"@self" to fetch the user's App Data or "@friends" to
        /// fetch App Data for the user's friends, or the ID as a string of one of the user's custom groups
        /// </param>
        /// <param name="appId">The ID of the application to fetch user App Data for
        /// or "@app" for the current application
        /// </param>
        /// <throws>  OpenSocialRequestException if there are any runtime issues with </throws>
        /// <summary>         establishing a RESTful or JSON-RPC connection or parsing the
        /// response that the container returns
        /// </summary>
        /// <throws>  JSONException </throws>
        /// <throws>  OAuthException </throws>
        /// <throws>  IOException </throws>
        /// <throws>  URISyntaxException </throws>
        private OpenSocialResponse fetchAppData(string userId, string groupId, string appId, IDictionary<string, OpenSocialRequestParameter> parameters)
        {
            if (string.IsNullOrEmpty(userId) || string.IsNullOrEmpty(groupId) || string.IsNullOrEmpty(appId))
                throw new OpenSocialRequestException("Invalid request parameters");

            OpenSocialRequest<AppData> r = newFetchPersonAppDataRequest(userId, groupId, appId, parameters);

            OpenSocialBatch batch = new OpenSocialBatch();
            batch.addRequest(r, "appdata");

            return batch.send(this);
        }

        private OpenSocialResponse updateAppData(string userId, IDictionary<string, string> data, bool useLegacyFormat)
        {
            OpenSocialRequest<AppData> r = newUpdatePersonAppDataRequest(userId, data, useLegacyFormat);

            OpenSocialBatch batch = new OpenSocialBatch();
            batch.addRequest(r, "appdata");

            return batch.send(this);
        }

        private OpenSocialResponse deleteAppData(string userId, IDictionary<string, OpenSocialRequestParameter> parameters)
        {
            OpenSocialRequest<AppData> r = newDeletePersonAppDataRequest(userId, parameters);

            OpenSocialBatch batch = new OpenSocialBatch();
            batch.addRequest(r, "appdata");

            return batch.send(this);
        }
        #endregion


        #region newFetch*Request
        /// <summary> Creates and returns a new OpenSocialRequest object for retrieving the
        /// profile information for the viewer.
        /// </summary>
        public static OpenSocialRequest<Person> newFetchPersonRequest()
        {
            return newFetchPeopleRequest("@me", "@self");
        }

        /// <summary> Creates and returns a new OpenSocialRequest object for retrieving the
        /// friends of the viewer.
        /// </summary>
        public static OpenSocialRequest<Person> newFetchFriendsRequest()
        {
            return newFetchFriendsRequest("@me", null);
        }

        /// <summary> Creates and returns a new OpenSocialRequest object for retrieving the
        /// profile information for a single person.
        /// </summary>
        /// <param name="userId">OpenSocial ID of the request's target
        /// </param>
        public static OpenSocialRequest<Person> newFetchPersonRequest(string userId)
        {
            return newFetchPeopleRequest(userId, "@self");
        }

        /// <summary> Creates and returns a new OpenSocialRequest object for retrieving the
        /// friends of the given user.
        /// </summary>
        /// <param name="userId">OpenSocial ID of the request's target
        /// </param>
        public static OpenSocialRequest<Person> newFetchFriendsRequest(string userId)
        {
            return newFetchFriendsRequest(userId, null);
        }

        /// <summary> Creates and returns a new OpenSocialRequest object for retrieving the
        /// friends of the given user.
        /// </summary>
        /// <param name="userId">OpenSocial ID of the request's target
        /// </param>
        /// <param name="parameters">Object containing any parameters to be passed along  with the request
        /// </param>
        public static OpenSocialRequest<Person> newFetchFriendsRequest(string userId, IDictionary<string, OpenSocialRequestParameter> parameters)
        {
            return newFetchPeopleRequest(userId, "@friends", parameters);
        }

        /// <summary> Creates and returns a new OpenSocialRequest object for retrieving the
        /// person or group of people selected by the arguments.
        /// </summary>
        /// <param name="userId">OpenSocial ID of the request's target
        /// </param>
        /// <param name="groupId">"@self" to fetch the user's profile details or "@friends"
        /// to fetch the user's friend list, or the ID as a string of one of the user's custom groups
        /// </param>
        public static OpenSocialRequest<Person> newFetchPeopleRequest(string userId, string groupId)
        {
            return newFetchPeopleRequest(userId, groupId, null);
        }

        /// <summary> Creates and returns a new OpenSocialRequest object for retrieving the
        /// person or group of people selected by the arguments.
        /// </summary>
        /// <param name="userId">OpenSocial ID of the request's target
        /// </param>
        /// <param name="groupId">"@self" to fetch the user's profile details or "@friends"
        /// to fetch the user's friend list, or the ID as a string of one of the user's custom groups
        /// </param>
        /// <param name="parameters">Object containing any parameters to be passed along  with the request
        /// </param>
        private static OpenSocialRequest<Person> newFetchPeopleRequest(string userId, string groupId, IDictionary<string, OpenSocialRequestParameter> parameters)
        {
            OpenSocialRequest<Person> r = new OpenSocialRequest<Person>("people", "people.get");
            if (parameters != null)
                r.Parameters = parameters;
            r.addParameter("groupId", groupId);
            r.addParameter("userId", userId);

            return r;
        }

        /// <summary> Creates and returns a new OpenSocialRequest object for retrieving the
        /// persistent App Data for the person or group of people selected by the
        /// arguments for the specified application.
        /// </summary>
        /// <param name="userId">OpenSocial ID of the request's target
        /// </param>
        /// <param name="groupId">"@self" to fetch the user's App Data or "@friends" to
        /// fetch App Data for the user's friends, or the ID as a string of one of the user's custom groups
        /// </param>
        /// <param name="appId">The ID of the application to fetch user App Data for
        /// or "@app" for the current application
        /// </param>
        public static OpenSocialRequest<AppData> newFetchPersonAppDataRequest(string userId, string groupId, string appId, IDictionary<string, OpenSocialRequestParameter> parameters)
        {
            OpenSocialRequest<AppData> r = new OpenSocialRequest<AppData>("appdata", "appdata.get");

            if (parameters != null)
                r.Parameters = parameters;

            r.addParameter("groupId", groupId);
            r.addParameter("userId", userId);
            r.addParameter("appId", appId);

            return r;
        }

        /// <summary> Creates and returns a new OpenSocialRequest object for retrieving the
        /// persistent App Data for the person or group of people selected by the
        /// arguments for the current application.
        /// 
        /// </summary>
        /// <param name="userId">OpenSocial ID of the request's target
        /// </param>
        /// <param name="groupId">"@self" to fetch the user's App Data or "@friends" to
        /// fetch App Data for the user's friends, or the ID as a string of one of the user's custom groups
        /// </param>
        public static OpenSocialRequest<AppData> newFetchPersonAppDataRequest(string userId, string groupId)
        {
            return newFetchPersonAppDataRequest(userId, groupId, "@app", null);
        }

        public static OpenSocialRequest<AppData> newUpdatePersonAppDataRequest(
            string userId, IDictionary<string, string> data, bool useLegacyFormat)
        {
            OpenSocialRequest<AppData> r = new OpenSocialRequest<AppData>("appdata", "PUT", "appdata.update");
            r.addParameter("groupId", "@self");
            r.addParameter("userId", userId);
            r.addParameter("appId", "@app");

            if (useLegacyFormat)
            {
                r.addParameter("data", new Jayrock.Json.JsonObject((System.Collections.IDictionary)data).ToString());
            }
            else
            {

                Jayrock.Json.JsonObject obj = new Jayrock.Json.JsonObject();
                Jayrock.Json.JsonArray arr = new Jayrock.Json.JsonArray();

                foreach (KeyValuePair<string, string> kvp in data)
                {


                    Jayrock.Json.JsonObject o = new Jayrock.Json.JsonObject();
                    o["key"] = kvp.Key;
                    o["value"] = kvp.Value;
                    arr.Add(o);
                }

                obj["appData"] = arr;

                r.addParameter("data", obj.ToString());
            }

            r.addParameter("fields", new List<string>(data.Keys).ToArray());

            return r;
        }

        /// <summary> Creates and returns a new OpenSocialRequest object for deleting from the
        /// persistent App Data for the person selected by the arguments.
        /// </summary>
        /// <param name="userId">OpenSocial ID of the request's target
        /// </param>
        /// <param name="parameters">The keys to delete
        /// </param>
        public static OpenSocialRequest<AppData> newDeletePersonAppDataRequest(string userId, IDictionary<string, OpenSocialRequestParameter> parameters)
        {
            OpenSocialRequest<AppData> r = new OpenSocialRequest<AppData>("appdata", "DELETE", "appdata.delete");

            if (parameters != null)
                r.Parameters = parameters;

            r.addParameter("groupId", "@self");
            r.addParameter("userId", userId);
            r.addParameter("appId", "@app");

            return r;
        }
        #endregion


        #region Activities
        /// <summary>
        /// Method to return all activities for all apps for the current user (@me)
        /// </summary>
        public Response<Activity> fetchActivities()
        {
            return fetchActivities("@me", "@self", "");
        }

        /// <summary>
        /// Method to return all activities for the current user (@me) for the current app (@app)
        /// </summary>
        public Response<Activity> fetchActivitiesForApp()
        {
            return fetchActivities("@me", "@self", "@app");
        }

        /// <summary>
        /// Method to return all activities for the current user (@me) for the current app (@app)
        /// </summary>
        public Response<Activity> fetchActivitiesForApp(string userId)
        {
            return fetchActivities(userId, "@self", "@app");
        }

        /// <summary>
        /// Method to return all activities for the user represented by the user Id
        /// </summary>
        public Response<Activity> fetchActivitiesForPerson(string userId)
        {
            return fetchActivities(userId, "@self", "");
        }

        /// <summary>
        /// Method to return all activities of all friends of the user represented by the user Id
        /// </summary>
        public Response<Activity> fetchActivitiesForFriends(string userId)
        {
            return fetchActivities(userId, "@friends", "");
        }

        /// <summary>
        /// Method to fetch the activities for the specified group pertaining to the specified user for the specified app
        /// </summary>
        public Response<Activity> fetchActivities(string userId, string groupId, string appId)
        {
            return fetchActivities(userId, groupId, appId, null);

        }

        /// <summary>
        /// Method to fetch the activities for the specified group pertaining to the specified user for the specified app
        /// </summary>
        public Response<Activity> fetchActivities(string userId, string groupId, string appId, IDictionary<string, OpenSocialRequestParameter> parameters)
        {
            if (string.IsNullOrEmpty(userId) || string.IsNullOrEmpty(groupId))
                throw new OpenSocialRequestException("Invalid request parameters");

            OpenSocialRequest<Activity> r = newFetchActivitiesRequest(userId, groupId, appId, parameters);

            OpenSocialBatch batch = new OpenSocialBatch();
            batch.addRequest(r, "activities");

            OpenSocialResponse response = batch.send(this);
            return response.getItemAsActivityCollection("activities", UseRpc);

        }

        public static OpenSocialRequest<Activity> newFetchActivitiesRequest(string userId, string groupId, string appId, IDictionary<string, OpenSocialRequestParameter> parameters)
        {
            OpenSocialRequest<Activity> r = new OpenSocialRequest<Activity>("activities", "activity.get");
            if (parameters != null)
                r.Parameters = parameters;
            r.addParameter("userId", userId);
            r.addParameter("groupId", groupId);

            // If appId is empty, retrieve all the activities for the userId and groupId
            if (!string.IsNullOrEmpty(appId))
                r.addParameter("appId", appId);

            return r;
        }

        /// <summary>
        /// Creates and returns a new OpenSocialRequest object for retrieving the activities for the specified user, group and app
        /// </summary>
        public static OpenSocialRequest<Activity> newFetchActivitiesRequest(string userId, string groupId, string appId)
        {
            return newFetchActivitiesRequest(userId, groupId, appId, null);
        }

        /// <summary>
        /// Method to create an activity for the current user.
        /// </summary>
        public void createActivity(string title, string body)
        {
            createActivity("@me", "", title, body);
        }

        /// <summary>
        /// Method to create an activity for the user represented by the user Id for the app represented by the app Id
        /// </summary>
        public void createActivity(string userId, string appId, string title, string body)
        {
            if (string.IsNullOrEmpty(userId))
                throw new OpenSocialRequestException("Invalid request parameters");

            // Create a Dictionary with the data required for the activities: mainly title and body
            Dictionary<string, string> activityData = new Dictionary<string, string>
            {
                {"title", title},
                {"body", body}
            };

            OpenSocialRequest<Activity> r = newCreateActivityRequest(userId, "", activityData);

            OpenSocialBatch batch = new OpenSocialBatch();
            batch.addRequest(r, "activities");

            OpenSocialResponse response = batch.send(this);
        }

        /// <summary>
        /// Creates and returns a new OpenSocialRequest object for creating an activity for the specified user, group and app
        /// </summary>
        public static OpenSocialRequest<Activity> newCreateActivityRequest(string userId, string appId,
            IDictionary<string, string> data)
        {
            OpenSocialRequest<Activity> r = new OpenSocialRequest<Activity>("activities", "POST", "activity.create");
            r.addParameter("userId", userId);

            // If appId is empty, retrieve all the activities for the userId and groupId
            if (!string.IsNullOrEmpty(appId))
            {
                r.addParameter("appId", appId);
            }

            r.addParameter("data", new Jayrock.Json.JsonObject((IDictionary)data).ToString());
            return r;
        }

        public Activity createActivity(Activity activity)
        {
            return createActivity("@me", activity);
        }

        public Activity createActivity(string userId, Activity activity)
        {
            return createActivity(userId, "@self", "@app", activity);
        }

        public Activity createActivity(string userId, string groupId, string appId, Activity activity)
        {
            if (null == activity || string.IsNullOrEmpty(userId) || string.IsNullOrEmpty(groupId) || string.IsNullOrEmpty(appId))
                throw new OpenSocialRequestException("Invalid request parameters");

            OpenSocialRequest<Activity> r = newCreateActivityRequest(userId, groupId, appId, activity);

            OpenSocialBatch batch = new OpenSocialBatch();
            batch.addRequest(r, "request");

            OpenSocialResponse response = batch.send(this);
            return response.getItemAsObject<Activity>("request", "activities");
        }

        public static OpenSocialRequest<Activity> newCreateActivityRequest(string userId, string groupId, string appId, Activity activity)
        {
            OpenSocialRequest<Activity> r = new OpenSocialRequest<Activity>("activities", "POST", "activities.create");
            r.addParameter("userId", userId);
            r.addParameter("groupId", groupId);
            r.addParameter("appId", appId);

            Jayrock.Json.JsonObject activityJson = new Jayrock.Json.JsonObject();

            if (!string.IsNullOrEmpty(activity.TitleId))
            {
                activityJson["titleId"] = activity.TitleId;
            }

            if (null != activity.TemplateParameters && activity.TemplateParameters.Count > 0)
            {
                Jayrock.Json.JsonArray arr = new Jayrock.Json.JsonArray();
                foreach (string key in activity.TemplateParameters.Keys)
                {
                    Jayrock.Json.JsonObject obj = new Jayrock.Json.JsonObject();
                    obj["key"] = key;
                    obj["value"] = activity.TemplateParameters[key];
                    arr.Add(obj);
                }

                Jayrock.Json.JsonObject templateParams = new Jayrock.Json.JsonObject();
                templateParams["msParameters"] = arr;

                activityJson["templateParams"] = templateParams;
            }

            r.addParameter("data", activityJson.ToString());

            return r;
        }
        #endregion


        #region Albums
        public Response<Album> fetchAlbums()
        {
            return fetchAlbums("@me");
        }

        public Response<Album> fetchAlbums(string userId)
        {
            return fetchAlbums(userId, "@self", null, null);
        }

        public Response<Album> fetchAlbums(string userId, string groupId, string albumId, IDictionary<string, OpenSocialRequestParameter> parameters)
        {
            if (string.IsNullOrEmpty(userId) || string.IsNullOrEmpty(groupId))
                throw new OpenSocialRequestException("Invalid request parameters");

            OpenSocialRequest<Album> r = newFetchAlbumsRequest(userId, groupId, albumId, parameters);

            OpenSocialBatch batch = new OpenSocialBatch();
            batch.addRequest(r, "albums");

            OpenSocialResponse response = batch.send(this);

            Response<Album> toReturn = null;

            if (response.isItemEntryAnArray("albums"))
            {
                toReturn = response.getItemAsAlbumCollection("albums", UseRpc);
            }
            else
            {
                toReturn = new Response<Album>();
                toReturn.Add(response.getItemAsAlbum("albums", UseRpc));
            }

            return toReturn;
        }

        public static OpenSocialRequest<Album> newFetchAlbumsRequest(string userId)
        {
            return newFetchAlbumsRequest(userId, "@self", null, null);
        }

        public static OpenSocialRequest<Album> newFetchAlbumsRequest(string userId, string groupId, string albumId, IDictionary<string, OpenSocialRequestParameter> parameters)
        {
            OpenSocialRequest<Album> r = new OpenSocialRequest<Album>("albums", "albums.get");
            if (parameters != null)
                r.Parameters = parameters;

            r.addParameter("userId", userId);
            r.addParameter("groupId", groupId);

            if (!string.IsNullOrEmpty(albumId))
                r.addParameter("albumId", albumId);

            return r;
        }






        public Album createAlbum(Album album)
        {
            return createAlbum("@me", "@self", album);
        }

        public Album createAlbum(string userId, string groupId, Album album)
        {
            if (string.IsNullOrEmpty(userId))
                throw new OpenSocialRequestException("Invalid request parameters");

            OpenSocialRequest<Album> r = newCreateAlbumRequest(userId, groupId, album);

            OpenSocialBatch batch = new OpenSocialBatch();
            batch.addRequest(r, "request");

            OpenSocialResponse response = batch.send(this); // TODO: Must POST "album" // Cf. /Albums support in OpenSocial.htm
            return response.getItemAsObject<Album>("request", "album"); // TODO: The result is just the Id so set it to album! + Don't return anything!?!
        }

        public static OpenSocialRequest<Album> newCreateAlbumRequest(string userId, string groupId, Album album)
        {
            OpenSocialRequest<Album> r = new OpenSocialRequest<Album>("albums", "POST", "albums.create");
            r.addParameter("userId", userId);
            r.addParameter("groupId", groupId);

            r.addParameter("data", new Jayrock.Json.JsonObject((IDictionary)album.getData()).ToString());
            return r;
        }
        #endregion


        #region Notifications
        public Notification createNotification(Notification notification)
        {
            return createNotification("@me", notification);
        }

        public Notification createNotification(string userId, Notification notification)
        {
            return createNotification("@me", "@self", notification);
        }

        public Notification createNotification(string userId, string groupId, Notification notification)
        {
            if (string.IsNullOrEmpty(userId))
                throw new OpenSocialRequestException("Invalid request parameters");

            OpenSocialRequest<Notification> r = newCreateNotificationRequest(userId, groupId, notification);

            OpenSocialBatch batch = new OpenSocialBatch();
            batch.addRequest(r, "request");

            OpenSocialResponse response = batch.send(this);
            return response.getItemAsObject<Notification>("request", "notifications");
        }

        // specific to MySpace
        public static OpenSocialRequest<Notification> newCreateNotificationRequest(string userId, string groupId, Notification notification)
        {
            OpenSocialRequest<Notification> r = new OpenSocialRequest<Notification>("notifications", "POST", "notifications.create");
            r.addParameter("userId", userId);
            r.addParameter("groupId", groupId);

            Jayrock.Json.JsonObject notificationJson = new Jayrock.Json.JsonObject();

            if (null != notification.MediaItems && notification.MediaItems.Count > 0)
            {
                Jayrock.Json.JsonArray arr = new Jayrock.Json.JsonArray();
                foreach (string mi in notification.MediaItems)
                {
                    Jayrock.Json.JsonObject obj = new Jayrock.Json.JsonObject();
                    obj["msMediaItemUri"] = mi;
                    arr.Add(obj);
                }
                notificationJson["mediaItems"] = arr;
            }
            if (null != notification.RecipientIds && notification.RecipientIds.Count > 0)
            {
                notificationJson["recipientIds"] = new Jayrock.Json.JsonArray(notification.RecipientIds);
            }
            if (null != notification.TemplateParameters && notification.TemplateParameters.Count > 0)
            {
                Jayrock.Json.JsonArray arr = new Jayrock.Json.JsonArray();
                foreach (string key in notification.TemplateParameters.Keys)
                {
                    Jayrock.Json.JsonObject obj = new Jayrock.Json.JsonObject();
                    obj["key"] = key;
                    obj["value"] = notification.TemplateParameters[key];
                    arr.Add(obj);
                }
                notificationJson["templateParameters"] = arr;
            }

            r.addParameter("data", notificationJson.ToString());

            return r;
        }
        #endregion


        #region StatusMood
        public Response<StatusMood> fetchStatusMood()
        {
            return fetchStatusMood("@me");
        }

        public Response<StatusMood> fetchStatusMood(string userId)
        {
            return fetchStatusMood(userId, "@self");
        }

        public Response<StatusMood> fetchStatusMood(string userId, string groupId)
        {
            if (string.IsNullOrEmpty(userId) || string.IsNullOrEmpty(groupId))
                throw new OpenSocialRequestException("Invalid request parameters");

            OpenSocialRequest<StatusMood> r = newFetchStatusMoodRequest(userId, groupId);

            OpenSocialBatch batch = new OpenSocialBatch();
            batch.addRequest(r, "statusMood");

            OpenSocialResponse response = batch.send(this);

            Response<StatusMood> toReturn = null;

            if (groupId.CompareTo("@self") == 0)
            {
                toReturn = new Response<StatusMood>();
                toReturn.Add(response.getItemAsStatusMood("statusMood", UseRpc));
            }
            else if (groupId.CompareTo("@supportedMood") == 0)
            {
                toReturn = response.getItemAsStatusMoodCollection(groupId, UseRpc);
            }
            else
            {
                toReturn = response.getItemAsStatusMoodCollection("statusMood", UseRpc);
            }

            return toReturn;
        }

        public void updateStatusMood(StatusMood statusMood)
        {
            updateStatusMood("@me", statusMood);
        }

        public void updateStatusMood(string userId, StatusMood statusMood)
        {
            updateStatusMood(userId, "@self", statusMood);
        }

        public void updateStatusMood(string userId, string groupId, StatusMood statusMood)
        {
            if (string.IsNullOrEmpty(userId) || string.IsNullOrEmpty(groupId) || null == statusMood)
                throw new OpenSocialRequestException("Invalid request parameters");

            OpenSocialRequest<StatusMood> r = newUpdateStatusMoodRequest(userId, groupId, statusMood);

            OpenSocialBatch batch = new OpenSocialBatch();
            batch.addRequest(r, "request");

            OpenSocialResponse response = batch.send(this);

        }

        public static OpenSocialRequest<StatusMood> newUpdateStatusMoodRequest(string userId, string groupId, StatusMood statusMood)
        {
            OpenSocialRequest<StatusMood> r = new OpenSocialRequest<StatusMood>("statusmood", "POST", "statusmood.update");
            r.addParameter("userId", userId);
            r.addParameter("groupId", groupId);

            r.addParameter("data", new Jayrock.Json.JsonObject((IDictionary)statusMood.getData()).ToString());
            return r;
        }

        public static OpenSocialRequest<StatusMood> newFetchStatusMoodRequest(string userId, string groupId)
        {
            OpenSocialRequest<StatusMood> r = new OpenSocialRequest<StatusMood>("statusmood", "statusmood.get");

            r.addParameter("userId", userId);
            r.addParameter("groupId", groupId);

            return r;
        }
        #endregion


        #region StatusMoodComments
        public Response<StatusMoodComment> fetchStatusMoodComments(string statusId)
        {
            return fetchStatusMoodComments("@me", statusId);
        }

        public Response<StatusMoodComment> fetchStatusMoodComments(string statusId, IDictionary<string, OpenSocialRequestParameter> parameters)
        {
            return fetchStatusMoodComments("@me", statusId, parameters);
        }

        public Response<StatusMoodComment> fetchStatusMoodComments(DateTime postedDate)
        {
            return fetchStatusMoodComments("@me", postedDate);
        }

        public Response<StatusMoodComment> fetchStatusMoodComments(DateTime postedDate, IDictionary<string, OpenSocialRequestParameter> parameters)
        {
            return fetchStatusMoodComments("@me", postedDate, parameters);
        }
        
        public Response<StatusMoodComment> fetchStatusMoodComments(string userId, string statusId)
        {
            return fetchStatusMoodComments(userId, "@self", statusId);
        }

        public Response<StatusMoodComment> fetchStatusMoodComments(string userId, string statusId, IDictionary<string, OpenSocialRequestParameter> parameters)
        {
            return fetchStatusMoodComments(userId, "@self", statusId, parameters);
        }

        public Response<StatusMoodComment> fetchStatusMoodComments(string userId, DateTime postedDate)
        {
            return fetchStatusMoodComments(userId, "@self", postedDate);
        }

        public Response<StatusMoodComment> fetchStatusMoodComments(string userId, DateTime postedDate, IDictionary<string, OpenSocialRequestParameter> parameters)
        {
            return fetchStatusMoodComments(userId, "@self", postedDate, parameters);
        }

        public Response<StatusMoodComment> fetchStatusMoodComments(string userId, string groupId, string statusId)
        {
            return fetchStatusMoodComments(userId, groupId, statusId, null);
        }

        public Response<StatusMoodComment> fetchStatusMoodComments(string userId, string groupId, string statusId, IDictionary<string, OpenSocialRequestParameter> parameters)
        {
            return fetchStatusMoodComments(userId, groupId, statusId, null, parameters);
        }

        public Response<StatusMoodComment> fetchStatusMoodComments(string userId, string groupId, DateTime postedDate)
        {
            return fetchStatusMoodComments(userId, groupId, null, postedDate, null);
        }

        public Response<StatusMoodComment> fetchStatusMoodComments(string userId, string groupId, DateTime postedDate, IDictionary<string, OpenSocialRequestParameter> parameters)
        {
            return fetchStatusMoodComments(userId, groupId, null, postedDate, parameters);
        }

        private Response<StatusMoodComment> fetchStatusMoodComments(
            string userId, 
            string groupId, 
            string statusId, 
            DateTime? postedDate,
            IDictionary<string, OpenSocialRequestParameter> parameters)
        {
            if (
                    string.IsNullOrEmpty(userId) ||
                    string.IsNullOrEmpty(groupId) ||
                    (string.IsNullOrEmpty(statusId) && null == postedDate))
            {
                throw new OpenSocialRequestException("Invalid request parameters");
            }

            OpenSocialRequest<StatusMoodComment> r = newFetchStatusMoodCommentsRequest(userId, groupId, statusId, postedDate, parameters);

            OpenSocialBatch batch = new OpenSocialBatch();
            batch.addRequest(r, "statusMoodComments");

            OpenSocialResponse response = batch.send(this);

            Response<StatusMoodComment> toReturn = response.getItemAsStatusMoodCommentsCollection("statusMoodComments", UseRpc);

            return toReturn;
        }

        private static OpenSocialRequest<StatusMoodComment> newFetchStatusMoodCommentsRequest(
            string userId, 
            string groupId, 
            string statusId, 
            DateTime? postedDate,
            IDictionary<string, OpenSocialRequestParameter> parameters)
        {
            OpenSocialRequest<StatusMoodComment> r = new OpenSocialRequest<StatusMoodComment>("statusmoodcomments", "statusmoodcomments.get");

            if (null != parameters)
            {
                r.Parameters = parameters;
            }

            r.addParameter("userId", userId);
            r.addParameter("groupId", groupId);

            if (null == postedDate)
            {
                r.addParameter("statusId", statusId);
            }
            else
            {
                r.addParameter("postedDate", postedDate.ToString());
            }

            return r;
        }

        public void createStatusMoodComment(string userId, string statusId, StatusMoodComment statusMoodComment)
        {
            createStatusMoodComment(userId, "@self", statusId, statusMoodComment);
        }

        public void createStatusMoodComment(string userId, string groupId, string statusId, StatusMoodComment statusMoodComment)
        {
            createStatusMoodComment(userId, "@self", statusId, null, statusMoodComment);
        }

        public void createStatusMoodComment(string userId, DateTime postedDate, StatusMoodComment statusMoodComment)
        {
            createStatusMoodComment(userId, "@self", postedDate, statusMoodComment);
        }

        public void createStatusMoodComment(string userId, string groupId, DateTime postedDate, StatusMoodComment statusMoodComment)
        {
            createStatusMoodComment(userId, "@self", null, postedDate, statusMoodComment);
        }

        private void createStatusMoodComment(string userId, string groupId, string statusId, DateTime? postedDate, StatusMoodComment statusMoodComment)
        {
            if (
                    string.IsNullOrEmpty(userId) ||
                    string.IsNullOrEmpty(groupId) ||
                    (string.IsNullOrEmpty(statusId) && null == postedDate) ||
                    null == statusMoodComment ||
                    string.IsNullOrEmpty(statusMoodComment.Body)
                )
            {
                throw new OpenSocialRequestException("Invalid request parameters");
            }

            OpenSocialRequest<StatusMoodComment> r = newCreateStatusMoodCommentRequest(userId, groupId, statusId, postedDate, statusMoodComment);

            OpenSocialBatch batch = new OpenSocialBatch();
            batch.addRequest(r, "request");

            OpenSocialResponse response = batch.send(this);
        }

        public static OpenSocialRequest<StatusMoodComment> newCreateStatusMoodCommentRequest(
            string userId, 
            string groupId,
            string statusId,
            DateTime? postedDate,
            StatusMoodComment statusMoodComment)
        {
            OpenSocialRequest<StatusMoodComment> r = new OpenSocialRequest<StatusMoodComment>("statusmoodcomments", "POST", "statusmoodcomments.update");
            r.addParameter("userId", userId);

            if (!string.IsNullOrEmpty(statusId))
            {
                r.addParameter("statusId", statusId);
            }
            else if (null != postedDate)
            {
                r.addParameter("postedDate", postedDate.ToString());
                //r.addParameter("postedDate", "Thu Feb 04 13:51:40 EST 2010");
            }
            r.addParameter("groupId", groupId);

            r.addParameter("data", new Jayrock.Json.JsonObject((IDictionary)statusMoodComment.getData()).ToString());
            return r;
        }
        #endregion


        #region MediaItems
        public Response<MediaItem> fetchMediaItems() // TODO: These first three methods are not supported by Orkut or MySpace >= 0.9 because it requires albumId
        {
            return fetchMediaItems("@me");
        }

        public Response<MediaItem> fetchMediaItems(string userId)
        {
            return fetchMediaItems(userId, "@self");
        }

        public Response<MediaItem> fetchMediaItems(string userId, string groupId)
        {
            return fetchMediaItems(userId, groupId, null);
        }

        public Response<MediaItem> fetchMediaItems(string userId, string groupId, string albumId)
        {
            return fetchMediaItems(userId, groupId, albumId, null);
        }

        public Response<MediaItem> fetchMediaItems(string userId, string groupId, string albumId, string mediaItemId)
        {
            return fetchMediaItems(userId, groupId, albumId, mediaItemId, null);
        }

        public Response<MediaItem> fetchMediaItems(string userId, string groupId, string albumId, string mediaItemId, IDictionary<string, OpenSocialRequestParameter> parameters)
        {
            if (string.IsNullOrEmpty(userId) || string.IsNullOrEmpty(groupId))
                throw new OpenSocialRequestException("Invalid request parameters");

            OpenSocialRequest<MediaItem> r = newFetchMediaItemsRequest(userId, groupId, albumId, mediaItemId, parameters);

            OpenSocialBatch batch = new OpenSocialBatch();
            batch.addRequest(r, "mediaItems");

            OpenSocialResponse response = batch.send(this);

            Response<MediaItem> toReturn = null;

            if (response.isItemEntryAnArray("mediaItems"))
            {
                toReturn = response.getItemAsMediaItemCollection("mediaItems", UseRpc);
            }
            else
            {
                toReturn = new Response<MediaItem>();
                toReturn.Add(response.getItemAsMediaItem("mediaItems", UseRpc));
            }

            return toReturn;
        }

        public static OpenSocialRequest<MediaItem> newFetchMediaItemsRequest(string userId, string groupId, string albumId)
        {
            return newFetchMediaItemsRequest(userId, groupId, albumId, null, null);
        }

        public static OpenSocialRequest<MediaItem> newFetchMediaItemsRequest(string userId, string groupId,
            string optionalAlbumId, string optionalMediaItemId, IDictionary<string, OpenSocialRequestParameter> parameters)
        {
            OpenSocialRequest<MediaItem> r = new OpenSocialRequest<MediaItem>("mediaitems", "mediaitems.get");
            if (parameters != null)
                r.Parameters = parameters;

            r.addParameter("userId", userId);
            r.addParameter("groupId", groupId);

            if (!string.IsNullOrEmpty(optionalAlbumId))
                r.addParameter("albumId", optionalAlbumId);
            if (!string.IsNullOrEmpty(optionalMediaItemId))
            {
                if (!string.IsNullOrEmpty(optionalAlbumId))
                    r.addParameter("mediaItemId", optionalMediaItemId);
                else
                    throw new OpenSocialRequestException("optionalAlbumId is required to get to a specific media item.");
            }

            return r;
        }

        public MediaItem uploadMediaItem(string albumId, MediaItem mediaItem, byte[] image, string contentType, string mediaType)
        {
            return uploadMediaItem("@me", "@self", albumId, mediaItem, image, contentType, mediaType);
        }

        public MediaItem uploadMediaItem(string userId, string groupId, string albumId, 
            MediaItem mediaItem, byte[] image, string contentType, string mediaType)
        {
            if (string.IsNullOrEmpty(userId) || string.IsNullOrEmpty(groupId) || string.IsNullOrEmpty(albumId)
                     || string.IsNullOrEmpty(contentType) || string.IsNullOrEmpty(mediaType))
                throw new OpenSocialRequestException("Invalid request parameters");

            OpenSocialRequest<MediaItem> r = newUploadMediaItemRequest(userId, groupId, albumId, mediaItem, image, contentType, mediaType);

            OpenSocialBatch batch = new OpenSocialBatch();
            batch.addRequest(r, "request");

            OpenSocialResponse response = batch.send(this); // TODO: Must POST "mediaitem" // Cf. Albums support in OpenSocial
            return response.getItemAsObject<MediaItem>("request", null); // TODO: The result is just the Id; so set it and don't return anything!?!
        }

        private static OpenSocialRequest<MediaItem> newUploadMediaItemRequest(string userId, string groupId, string albumId,
            MediaItem mediaItem, byte[] image, string contentType, string mediaType)
        {
            OpenSocialRequest<MediaItem> r = new OpenSocialRequest<MediaItem>("mediaitems", "POST", "mediaitems.upload");
            r.addParameter("userId", userId);
            r.addParameter("groupId", groupId);
            r.addParameter("albumId", albumId);

            string[] fields = mediaItem.fieldNames();
            foreach (string field in fields)
            {
                OpenSocialField f = mediaItem.getField(field);
                if (!f.IsComplex)
                {
                    r.addParameter(field, mediaItem.getField(field).StringValue);
                }
            }

            r.addParameter("mediaType", mediaType);
            r.addParameter("contentType", contentType);

            if (null != image)
            {
                r.addParameter("data", Convert.ToBase64String(image));
            }

            return r;
        }
        #endregion


        #region MediaItemComments
        public Response<MediaItemComment> fetchMediaItemComments(string albumId, string mediaItemId)
        {
            return fetchMediaItemComments("@me", albumId, mediaItemId);
        }

        public Response<MediaItemComment> fetchMediaItemComments(string albumId, string mediaItemId, IDictionary<string, OpenSocialRequestParameter> parameters)
        {
            return fetchMediaItemComments("@me", "@self", albumId, mediaItemId, parameters);
        }

        public Response<MediaItemComment> fetchMediaItemComments(string userId, string albumId, string mediaItemId)
        {
            return fetchMediaItemComments(userId, "@self", albumId, mediaItemId);
        }

        public Response<MediaItemComment> fetchMediaItemComments(string userId, string albumId, string mediaItemId, IDictionary<string, OpenSocialRequestParameter> parameters)
        {
            return fetchMediaItemComments(userId, "@self", albumId, mediaItemId, parameters);
        }

        public Response<MediaItemComment> fetchMediaItemComments(string userId, string groupId, string albumId, string mediaItemId)
        {
            return fetchMediaItemComments(userId, groupId, albumId, mediaItemId, null);
        }

        public Response<MediaItemComment> fetchMediaItemComments(
            string userId, 
            string groupId, 
            string albumId, 
            string mediaItemId,
            IDictionary<string, OpenSocialRequestParameter> parameters)
        {
            if (
                string.IsNullOrEmpty(userId) || 
                string.IsNullOrEmpty(groupId) ||
                string.IsNullOrEmpty(albumId) || 
                string.IsNullOrEmpty(mediaItemId))
                throw new OpenSocialRequestException("Invalid request parameters");

            OpenSocialRequest<MediaItemComment> r = newFetchMediaItemCommentsRequest(userId, groupId, albumId, mediaItemId, parameters);

            OpenSocialBatch batch = new OpenSocialBatch();
            batch.addRequest(r, "mediaItemComments");

            OpenSocialResponse response = batch.send(this);

            Response<MediaItemComment> toReturn = response.getItemAsMediaitemCommentsCollection("mediaItemComments", UseRpc);

            return toReturn;
        }

        public static OpenSocialRequest<MediaItemComment> newFetchMediaItemCommentsRequest(
            string userId, 
            string groupId, 
            string albumId, 
            string mediaItemId, 
            IDictionary<string, OpenSocialRequestParameter> parameters)
        {
            OpenSocialRequest<MediaItemComment> r = new OpenSocialRequest<MediaItemComment>("mediaitemcomments", "mediaitemcomments.get");

            if (parameters != null)
                r.Parameters = parameters;

            r.addParameter("userId", userId);
            r.addParameter("groupId", groupId);
            r.addParameter("albumId", albumId);
            r.addParameter("mediaItemId", mediaItemId);

            return r;
        }
        #endregion


        public string listMethods()
        {
            OpenSocialRequest<OpenSocialObject> r = new OpenSocialRequest<OpenSocialObject>("listMethods", "system.listMethods");

            OpenSocialBatch batch = new OpenSocialBatch();
            batch.addRequest(r, "system");

            OpenSocialResponse response = batch.send(this);
            return response.ToString();
        }
    }
}